﻿namespace Codeplex.Controls
{
    using System;
    using System.Diagnostics;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;

    [TemplatePart(Name = "TopLeftAdorner", Type = typeof(FrameworkElement))]
    [TemplatePart(Name = "TopCenterAdorner", Type = typeof(FrameworkElement))]
    [TemplatePart(Name = "TopRightAdorner", Type = typeof(FrameworkElement))]
    [TemplatePart(Name = "CenterLeftAdorner", Type = typeof(FrameworkElement))]
    [TemplatePart(Name = "CenterRightAdorner", Type = typeof(FrameworkElement))]
    [TemplatePart(Name = "BottomLeftAdorner", Type = typeof(FrameworkElement))]
    [TemplatePart(Name = "BottomCenterAdorner", Type = typeof(FrameworkElement))]
    [TemplatePart(Name = "BottomRightAdorner", Type = typeof(FrameworkElement))]
    public class Adorner : Control, IDisposable
    {
        private readonly FrameworkElement _associatedElement;

        private Popup _popup;

        private Border _contentBorder;
        private FrameworkElement _topLeftAdorner;
        private FrameworkElement _topCenterAdorner;
        private FrameworkElement _topRightAdorner;
        private FrameworkElement _centerLeftAdorner;
        private FrameworkElement _bottomLeftAdorner;

        /// <summary>
        /// Create an Adorner control that adds a border and popup elements surrounding a framework element.
        /// The concept is 'temporary' UI that appears around an associated element, similar to a context menu but surrounding rather
        /// than over the associated element. See OneNote's UI as an example of usage.
        /// </summary>
        /// <param name="associatedElement">Element to Adorn</param>
        public Adorner(FrameworkElement associatedElement)
        {
            DefaultStyleKey = typeof(Adorner);

            _associatedElement = associatedElement;

            _popup = new Popup
            {
                Child = this,
                IsOpen = true
            };

            _popup.LayoutUpdated += PopupLayoutUpdated;
        }

        void PopupLayoutUpdated(object sender, EventArgs e)
        {
            var topLeftContent = _associatedElement.TransformToVisual(Application.Current.RootVisual).Transform(new Point(0, 0));
            var bottomRightContent = _associatedElement.TransformToVisual(Application.Current.RootVisual).Transform(new Point(_associatedElement.ActualWidth, _associatedElement.ActualHeight));

            // Size the content border to fit the content
            _contentBorder.Width = bottomRightContent.X - topLeftContent.X;
            _contentBorder.Height = bottomRightContent.Y - topLeftContent.Y;

            // Determine the position of the popup taking into account the height and width of the top and left adorners
            var horizontalOffset = topLeftContent.X - Max(_topLeftAdorner.ActualWidth, _centerLeftAdorner.ActualWidth, _bottomLeftAdorner.ActualWidth);
            var verticalOffset = topLeftContent.Y - Max(_topLeftAdorner.ActualHeight, _topCenterAdorner.ActualHeight, _topRightAdorner.ActualHeight);

            // Check for difference to prevent infinite loops of LayoutUpdated
            // ReSharper disable RedundantCheckBeforeAssignment
            if (_popup.HorizontalOffset != horizontalOffset) _popup.HorizontalOffset = horizontalOffset;
            if (_popup.VerticalOffset != verticalOffset) _popup.VerticalOffset = verticalOffset;
            // ReSharper restore RedundantCheckBeforeAssignment
        }

        public override void OnApplyTemplate()
        {
            _contentBorder = GetTemplateChild("ContentBorder") as Border;
            _topLeftAdorner = GetTemplateChild("TopLeftAdorner") as FrameworkElement;
            _topCenterAdorner = GetTemplateChild("TopCenterAdorner") as FrameworkElement;
            _topRightAdorner = GetTemplateChild("TopRightAdorner") as FrameworkElement;
            _centerLeftAdorner = GetTemplateChild("CenterLeftAdorner") as FrameworkElement;
            _bottomLeftAdorner = GetTemplateChild("BottomLeftAdorner") as FrameworkElement;

            Debug.Assert(_contentBorder != null && _topCenterAdorner != null && _topLeftAdorner != null && _centerLeftAdorner != null && _bottomLeftAdorner != null && _topRightAdorner != null);
        }

        public void Dispose()
        {
            _popup.LayoutUpdated -= PopupLayoutUpdated;
            _popup.IsOpen = false;
            _popup = null;

            GC.SuppressFinalize(this);
        }

        private static double Max(double a, double b, double c)
        {
            return Math.Max(a, Math.Max(b, c));
        }
    }
}